//Headers
#include "entities.h"
#include <Box2D/Box2D.h>
#include <SDL/SDL.h>
#include "sdlfuncs.h"
#include "stb_image.h"

//Pi on 4
extern float pio4;

//Scale
extern int P_SCALE;

//######## Environ ########
//Rectangle
void EnvRect::specify(int X, int Y, int W, int H, int T, GLuint eTex, b2World* World)
{
    //Environment type
    bType = T;
    render = true;
    if(bType == 0)
    {
        render = false;
    }

    //Offsets
    box.x = X;
    box.y = Y;

    //Dimentions
    box.w = W;
    box.h = H;
    d_box.w = W;
    d_box.h = H;

    //Times texture is repeated
    re[0] = W/16;
    re[1] = H/16;

    //Set texture
    texture = eTex;

    //Box2D
    //World
    pWorld = World;
    // Define the ground body.
	b2BodyDef groundBodyDef;
	groundBodyDef.type = b2_staticBody;
	groundBodyDef.position.Set(float(float(X+float(W/2))/P_SCALE), float(float(Y+float(H/2))/P_SCALE));

	// Call the body factory which allocates memory for the ground body
	// from a pool and creates the ground box shape (also from a pool).
	// The body is also added to the world.
	groundBody = pWorld->CreateBody(&groundBodyDef);

	// Define the ground box shape.
	b2PolygonShape groundBox;

	// The extents are the half-widths of the box.
	groundBox.SetAsBox(float(float(W/P_SCALE)/2), float(float(H/P_SCALE)/2));

	// Add the ground fixture to the ground body.
	groundBody->CreateFixture(&groundBox, 0.0f);
}

SDL_Rect EnvRect::getbox()
{
    return box;
}

void EnvRect::show(SDL_Rect camera)
{
    if(render and check_collision(box, camera))
    {
        d_box.x = box.x - camera.x;
        d_box.y = box.y - camera.y;

        //OpenGL Render
        //glEnable(GL_ALPHA_TEST);
        //glAlphaFunc(GL_EQUAL, 1);
        //Move to offset
        glTranslated(d_box.x, d_box.y, 0);
        //Bind texture to call
        glBindTexture(GL_TEXTURE_2D, texture);
        //Begin Quad
        glBegin(GL_QUADS);
            //Set colour
            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            //Vertices in CLOCKWISE order with texture positions
            glTexCoord2f(0.000f, 0.000f   ); glVertex3f(0,           0,          0);
            glTexCoord2f(re[0],  0.000f   ); glVertex3f(d_box.w,     0,          0);
            glTexCoord2f(re[0],  re[1]    ); glVertex3f(d_box.w,     d_box.h,    0);
            glTexCoord2f(0.000f, re[1]    ); glVertex3f(0,           d_box.h,    0);
        //End Quad
        glEnd();
        glLoadIdentity();
    }
}

//Triangle
void EnvTri::specify(int X, int Y, int W, int H, int T, GLuint eTex, b2World* World)
{
    //Environment type
    bType = T;
    render = true;
    if(bType == 0)
    {
        render = false;
    }

    //Offsets
    box.x = X;
    box.y = Y;

    //Dimentions
    box.w = W;
    box.h = H;
    d_box.w = W;
    d_box.h = H;

    //Times texture is repeated
    re[0] = W/16;
    re[1] = H/16;

    //Set texture
    texture = eTex;

    //Box2D
    //World
    pWorld = World;
    //Define the ground body.
	b2BodyDef groundBodyDef;
	groundBodyDef.type = b2_staticBody;
	groundBodyDef.position.Set(float(float(X+float(W/2))/P_SCALE), float(float(Y+float(H/2))/P_SCALE));

	// Call the body factory which allocates memory for the ground body
	// from a pool and creates the ground box shape (also from a pool).
	// The body is also added to the world.
	groundBody = pWorld->CreateBody(&groundBodyDef);

	// Define the ground box shape.
	b2PolygonShape groundBox;

	// The extents are the half-widths of the box.
	groundBox.SetAsBox(float(float(W/P_SCALE)/2), float(float(H/P_SCALE)/2));

	// Add the ground fixture to the ground body.
	groundBody->CreateFixture(&groundBox, 0.0f);
}

SDL_Rect EnvTri::getbox()
{
    return box;
}

void EnvTri::show(SDL_Rect camera)
{
    if(render and check_collision(box, camera))
    {
        d_box.x = box.x - camera.x;
        d_box.y = box.y - camera.y;

        //OpenGL Render
        //glEnable(GL_ALPHA_TEST);
        //glAlphaFunc(GL_EQUAL, 1);
        //Move to offset
        glTranslated(d_box.x, d_box.y, 0);
        //Bind texture to call
        glBindTexture(GL_TEXTURE_2D, texture);
        //Begin Quad
        glBegin(GL_QUADS);
            //Set colour
            glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
            //Vertices in CLOCKWISE order with texture positions
            glTexCoord2f(0.000f, 0.000f   ); glVertex3f(0,           0,          0);
            glTexCoord2f(re[0],  0.000f   ); glVertex3f(d_box.w,     0,          0);
            glTexCoord2f(re[0],  re[1]    ); glVertex3f(d_box.w,     d_box.h,    0);
            glTexCoord2f(0.000f, re[1]    ); glVertex3f(0,           d_box.h,    0);
        //End Quad
        glEnd();
        glLoadIdentity();
    }
}


//######## Block ########
Block::Block(int X, int Y, int T, b2Vec2 V, b2World* World)
{
    //Attributes
    rot = 0.000f;
    type = T;

    R = 1.0f;
    G = 0.0f;
    B = 0.0f;

    //Stand on box
    s_box.x = X;
    s_box.y = Y;
    s_box.w = 32;
    s_box.h = 32;

    //Draw box
    d_box.x = X;
    d_box.y = Y;
    d_box.w = 46;
    d_box.h = 46;
    sl = 32;
    hsl = sl / 2;
    halfdiag = hsl * sqrt(2);

    //Box2D Physics
    //World
    pWorld = World;
    //Define the body.
	b2BodyDef pBodyDef;
	pBodyDef.type = b2_dynamicBody;
	pBodyDef.position.Set(float((X+hsl)/P_SCALE), float((Y+hsl)/P_SCALE));

	// Call the body factory which allocates memory for the ground body
	// from a pool and creates the ground box shape (also from a pool).
	// The body is also added to the world.
	pBody = pWorld->CreateBody(&pBodyDef);

	// Define the box shape.
	b2PolygonShape dBox;
	//Set shape
	dBox.SetAsBox(float(hsl/P_SCALE), float(hsl/P_SCALE));

	b2FixtureDef fDef;
	fDef.shape = &dBox;
	fDef.density = 0.5f;
	fDef.friction = 0.1f;

	// Add the ground fixture to the ground body.
	pBody->CreateFixture(&fDef);

	//Set velocity to initial velocity
	pBody->SetLinearVelocity(V);
}

SDL_Rect Block::getbox()
{
    return s_box;
}

void Block::update()
{
    Uint8 *keystates = SDL_GetKeyState(NULL);
    //Keys
    if(keystates[SDLK_z])
    {
        pBody->ApplyForce( b2Vec2(0,-400), pBody->GetWorldCenter() );
    }
    //Box
    pPos = pBody->GetPosition();
    s_box.x = int(pPos.x*P_SCALE + 0.5) - hsl;
    s_box.y = int(pPos.y*P_SCALE + 0.5) - hsl;
}

void Block::show(SDL_Rect camera)
{
    d_box.x = int(pPos.x*P_SCALE + 0.5) - hsl;
    d_box.y = int(pPos.y*P_SCALE + 0.5) - hsl;
    if(check_collision(d_box, camera))
    {
        d_box.x += hsl - camera.x;
        d_box.y += hsl - camera.y;
        rot = pBody->GetAngle();
        coords[0] = halfdiag*cos(pio4 + rot);
        coords[1] = halfdiag*sin(pio4 + rot);
        //OGL Draw
        //glEnable(GL_ALPHA_TEST);
        //glAlphaFunc(GL_EQUAL, 1);
        //Bind no texture to call
        glBindTexture(GL_TEXTURE_2D, NULL);
        //Move to offset
        glTranslated(d_box.x, d_box.y, 0);
        //Begin Poly
        glBegin(GL_POLYGON);
            //Set colour
            glColor4f(R, G, B, 1.0f);
            //Vertices in CLOCKWISE order with texture positions
            glVertex3f(coords[0]   ,coords[1]   ,0);
            glVertex3f(coords[1]   ,-1*coords[0],0);
            glVertex3f(-1*coords[0],-1*coords[1],0);
            glVertex3f(-1*coords[1],coords[0]   ,0);
        //End Poly
        glEnd();
        glLoadIdentity();
    }
}


//######## Player ########
Player::Player(int X, int Y, b2World* World, char* pFilename, char* gFilename)
{
    //Position
    box.x = X;
    box.y = Y;
    dir = 1;

    //Attributes
    P_HA = 50;
    P_VA = 10;

    //Set aim
    aimdown = false;

    //Gen GL Tex
    int width, height, n;
    //Load player texture
    unsigned char* data = stbi_load(pFilename, &width, &height, &n, 4);
    // request RGBA
    if (data != NULL)
    {
        // use image in data
        glGenTextures(1, &pTexture);
        glBindTexture(GL_TEXTURE_2D, pTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        //Get attributes
        box.w = width;
        d_box.w = width;
        box.h = height;
        d_box.h = height;
        //Free image data
        stbi_image_free(data);
    }
    //Load gun texture
    data = stbi_load(gFilename, &width, &height, &n, 4);
    // request RGBA
    if (data != NULL)
    {
        // use image in data
        glGenTextures(1, &gTexture);
        glBindTexture(GL_TEXTURE_2D, gTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        //Free image data
        stbi_image_free(data);
    }

    //Physics world
    pWorld = World;

    //Box2D
    // Define the ground body.
	b2BodyDef pBodyDef;
	pBodyDef.type = b2_dynamicBody;
	pBodyDef.position.Set(float(float(X+float(box.w/2))/P_SCALE), float(float(Y+float(box.h/2))/P_SCALE));

	// Call the body factory which allocates memory for the ground body
	// from a pool and creates the ground box shape (also from a pool).
	// The body is also added to the world.
	pBody = World->CreateBody(&pBodyDef);

	// Define the ground box shape.
	b2PolygonShape dBox;
	//Define vertices
	float pW = float(box.w/P_SCALE);
	float pH = float(box.h/P_SCALE);
	b2Vec2 pV[8];
	pV[0] = b2Vec2(-1*pW/6,-1*pH/2);
	pV[1] = b2Vec2(pW/6,-1*pH/2);
	pV[2] = b2Vec2(pW/2,-1*pH/6);
	pV[3] = b2Vec2(pW/2,pH/6);
	pV[4] = b2Vec2(pW/6,pH/2);
	pV[5] = b2Vec2(-1*pW/6,pH/2);
	pV[6] = b2Vec2(-1*pW/2,pH/6);
	pV[7] = b2Vec2(-1*pW/2,-pH/6);
	//Set shape using vertices
	dBox.Set(pV,8);
	//dBox.SetAsBox(float(float(W/P_SCALE)/2), float(float(H/P_SCALE)/2));

	b2FixtureDef fDef;
	fDef.shape = &dBox;
	fDef.density = 0.5f;
	fDef.friction = 0.1f;

	// Add the ground fixture to the ground body.
	pBody->CreateFixture(&fDef);

	//Set fixed rotation
	pBody->SetFixedRotation(true);

	//Foot sensor
	//Shape
	b2PolygonShape fBox;
	fBox.SetAsBox(pW - 0.1f, 0.1f, b2Vec2(0, -pH/2), 0);
	b2FixtureDef footDef;
	footDef.shape = &fBox;
	footDef.density = 0.0f;
	footDef.isSensor = true;
	foot = pBody->CreateFixture(&footDef);
}

void Player::handle_input()
{
    //Get key state array
    keystates = SDL_GetKeyState(NULL);
    //Keys
    if(keystates[SDLK_UP] and standing)
    {
        pBody->ApplyLinearImpulse( b2Vec2(0,-1*P_VA), pBody->GetWorldCenter() );
    }
    if(keystates[SDLK_DOWN])
    {
        aimdown = true;
    }
    else
    {
        aimdown = false;
    }
    if(keystates[SDLK_LEFT])
    {
        pBody->ApplyForce( b2Vec2(-1*P_HA,0), pBody->GetWorldCenter() );
        dir = 1;
    }
    if(keystates[SDLK_RIGHT])
    {
        pBody->ApplyForce( b2Vec2(P_HA,0), pBody->GetWorldCenter() );
        dir = 0;
    }
    if(keystates[SDLK_z])
    {
        //
    }
    if(keystates[SDLK_x])
    {
        if(!shot)
        {
            shot = true;
            blocks.push_back(Block(box.x + box.w - dir * (32 + box.w), box.y + (2 * box.h / 5), 0, b2Vec2(-1 * ((dir * 2) - 1) * 100,0), pWorld));
        }
    }
    else
    {
        shot = false;
    }
    if(keystates[SDLK_LSHIFT])
    {
        //
    }
    if(keystates[SDLK_SPACE])
    {
        //
    }
}

void Player::updatepos()
{
    nBlocks = blocks.size();
    //Update surface draw position
    posVec = pBody->GetPosition();
    velVec = pBody->GetLinearVelocity();
    box.x = int(float(posVec.x*P_SCALE) - float(box.w/2)+float(0.5));
    box.y = int(float(posVec.y*P_SCALE) - float(box.h/2)+float(0.5));
}

void Player::checkStanding(SDL_Rect env[], int N_SOLIDS)
{
    //Get array of boxes
    /*
    SDL_Rect envb[N_SOLIDS + nBlocks];
    for(int i = 0; i < N_SOLIDS; i++)
    {
        envb[i] = env[i];
    }
    for(int x = N_SOLIDS; x < (N_SOLIDS + nBlocks); x++)
    {
        envb[x] = blocks[x - N_SOLIDS].getbox();
    }
    //Check if standing on any box
    if(check_collisionstand(box, envb, (N_SOLIDS + nBlocks)))
    {
        standing = true;
    }
    else
    {
        standing = false;
    }
    *
    if(foot->)
    {
        standing = true;
    }
    else
    {
        standing = false;
    }
    /*
    flag = true;
    while(flag)
    {
        ContEdg = pBody->GetContactList();
    }
    */
}

SDL_Rect Player::getbox()
{
    return box;
}

void Player::show(SDL_Rect camera)
{
    //Update and draw child blocks
    for(int i = 0; i < nBlocks; i++)
    {
        blocks[i].update();
        blocks[i].show(camera);
    }
    //Update draw box
    d_box.x = box.x - camera.x;
    d_box.y = box.y - camera.y;
    //GL Drawing
    //glEnable(GL_ALPHA_TEST);
    //glAlphaFunc(GL_EQUAL, 1);
    //Move to offset
    glTranslated(d_box.x, d_box.y, 0);
    //Bind texture to call
    glBindTexture(GL_TEXTURE_2D, pTexture);
    //Start quad
    glBegin(GL_QUADS);
        //Set colour
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        //Vertices in CLOCKWISE order with texture positions
        glTexCoord2i(dir,   0);  glVertex3f(0,           0,          0);
        glTexCoord2i(1-dir, 0);  glVertex3f(d_box.w,     0,          0);
        glTexCoord2i(1-dir, 1);  glVertex3f(d_box.w,     d_box.h,    0);
        glTexCoord2i(dir,   1);  glVertex3f(0,           d_box.h,    0);
    glEnd();
    //Bind texture to call
    glBindTexture(GL_TEXTURE_2D, gTexture);
    //Start quad
    glBegin(GL_QUADS);
        //Vertices in CLOCKWISE order with texture positions
        if(aimdown)
        {
            glTexCoord2i(0,     1-dir);  glVertex3f(10,          38,         0);
            glTexCoord2i(0,     0+dir);  glVertex3f(22,          38,         0);
            glTexCoord2i(1,     0+dir);  glVertex3f(22,          69,         0);
            glTexCoord2i(1,     1-dir);  glVertex3f(10,          69,         0);
        }
        else
        {
            glTexCoord2i(0+dir, 0);      glVertex3f(16 - dir*31, 33,         0);
            glTexCoord2i(1-dir, 0);      glVertex3f(47 - dir*31, 33,         0);
            glTexCoord2i(1-dir, 1);      glVertex3f(47 - dir*31, 45,         0);
            glTexCoord2i(0+dir, 1);      glVertex3f(16 - dir*31, 45,         0);
        }
    glEnd();
    glLoadIdentity();
}
